package ms.jacrim.pddl;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.Serializable;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

import ms.jacrim.tropos.Actor;
import ms.jacrim.tropos.Composition;
import ms.jacrim.tropos.Dependency;
import ms.jacrim.tropos.Diagram;
import ms.jacrim.tropos.MeansEnd;
import ms.jacrim.tropos.Service;
import ms.jacrim.tropos.Actor.ActorType;
import ms.jacrim.tropos.Composition.Type;
import ms.jacrim.tropos.Dependency.DependencyType;
import ms.jacrim.tropos.Service.ServiceType;
import ms.utils.DataBag;
import ms.utils.NamedList;

public class PddlSolution implements Serializable, Iterable<PddlSolution.Action> {
	private static final long serialVersionUID = 1L;

	public static class PddlActor extends DataBag {
		private static final long serialVersionUID = 1L;
		public String Name;
		public final List<PddlGoal> Goals = new ArrayList<PddlGoal>();
		public final List<Action> Actions = new ArrayList<Action>();

		public String getName() {
			return Name;
		}
	}

	public static class PddlGoal extends DataBag {
		private static final long serialVersionUID = 1L;
		public String Name;

		public String getName() {
			return Name;
		}
	}

	public enum Functor {
		PASSES, SATISFIES, COMBINESAND2, COMBINESAND3, COMBINESOR2, COMBINESOR3, AND_DECOMPOSES2, AND_DECOMPOSES3, OR_DECOMPOSES2, OR_DECOMPOSES3
	};

	public class Action implements Serializable {
		private static final long serialVersionUID = 1L;

		private String name;
		private Functor functor;
		private List<String> argument = new ArrayList<String>();
		
		public final int index;

		public Action(int idx, String command) {
			index = idx;
			String[] s = command.split(" ");
			name = s[0];
			functor = Functor.valueOf(name);
			for (int i = 1; i < s.length; i++)
				argument.add(s[i]);
		}

		public String Name() {
			return name;
		}
 		
		public Functor getFunctor() {
			return functor;
		}
		
		public int ArgumentCount() {
			return argument.size();
		}

		public String Argument(int index) {
			return argument.get(index);
		}

		public void toString(StringBuilder res) {
			res.append(name + "(");
			for (int i = 0; i < argument.size(); i++) {
				if (i > 0)
					res.append(',');
				res.append(argument.get(i));
			}
			res.append(")");
		}

		@Override
		public String toString() {
			StringBuilder s = new StringBuilder();
			toString(s);
			return s.toString();
		}

	}

	@Retention(value = RetentionPolicy.RUNTIME)
	@Target(value = { ElementType.METHOD })
	static public @interface Translator {
		String Action();
	}

	static class Translators {

		static Actor createActor(PddlSolution solution, Diagram diagram, String name) {
			Actor result = null, source = null;
			Actor.ActorType type = ActorType.ROLE;

			if (solution.originDiagram != null && (source = solution.originDiagram.getActor(name, true)) != null) {
				name = source.getName();
				type = source.getActorType();
			}

			result = diagram.createActor(name, type);

			return result;
		}

		static Service createService(Actor a, PddlSolution solution, Diagram diagram, String name) {
			Service result = null, source = null;
			Service.ServiceType type = ServiceType.GOAL;
			if (solution.originDiagram != null && (source = solution.originDiagram.getService(name, true)) != null) {
				name = source.getName();
				type = source.getServiceType();
			}

			result = a.addService(name, type);

			return result;
		}

		@Translator(Action = "satisfies")
		public static void Satisfy(PddlSolution solution, Diagram diagram, Action action) {
			Actor a = createActor(solution, diagram, action.Argument(0));
			createService(a, solution, diagram, action.Argument(1));
		}

		@Translator(Action = "and_decomposes2;and_decomposes3;or_decomposes2;or_decomposes3")
		public static void And_decompose(PddlSolution solution, Diagram diagram, Action action) throws Exception {
			Actor a = createActor(solution, diagram, action.Argument(0));
			Service g = createService(a, solution, diagram, action.Argument(1));
			// a.getServices().add(g);

			Composition.Type t = action.Name().startsWith("AND") ? Type.AND : Type.OR;

			for (int i = 2; i < action.ArgumentCount(); i++) {
				Service g1 = createService(a, solution, diagram, action.Argument(i));
				// a.getServices().add(g1);
				// a.addService(g1);
				Composition c1 = new Composition(solution.SolutionDiagram(), g, g1);
				c1.setCompositionType(t);
				solution.SolutionDiagram().addObject(c1);
			}
		}

		@Translator(Action = "passes")
		public static void Passes(PddlSolution solution, Diagram diagram, Action action) throws Exception {
			Actor a = createActor(solution, diagram, action.Argument(0));
			Actor b = createActor(solution, diagram, action.Argument(1));
			Service g1 = createService(a, solution, diagram, action.Argument(2));
			createService(b, solution, diagram, action.Argument(2));
			Dependency d = new Dependency(solution.SolutionDiagram(), g1, b);
			d.setDependencyType(DependencyType.DELEGATION_EXECUTION);
			solution.SolutionDiagram().addObject(d);
		}

		@Translator(Action = "means_end")
		public static void MeansEnd(PddlSolution solution, Diagram diagram, Action action) throws Exception {
			Diagram d = solution.SolutionDiagram();
			Actor p = createActor(solution, diagram, action.Argument(0));
			Actor r = createActor(solution, diagram, action.Argument(1));
			Service g1 = createService(p, solution, diagram, action.Argument(2));
			Service g2 = createService(r, solution, diagram, action.Argument(3));
			if (g1 != null && g2 != null) {
				MeansEnd m = new MeansEnd(d, g1, g2);
				d.addObject(m);
			}

		}
	}

	private List<Action> actions = new ArrayList<Action>();
	private transient Map<String, Method> actionTranslator = new HashMap<String, Method>();
	private transient Diagram originDiagram = null;
	private transient Diagram solutionDiagram;
	private NamedList<PddlActor> actors;
	private NamedList<PddlGoal> goals;
	private String name;

	public PddlSolution() {
		Translator t;
		for (Method m : Translators.class.getMethods())
			if ((t = m.getAnnotation(Translator.class)) != null) {
				for (String k : t.Action().toUpperCase().split(";"))
					actionTranslator.put(k, m);
			}
	}

	public PddlSolution(Diagram originDiagram, String solutionFile) throws FileNotFoundException {
		this();
		this.originDiagram = originDiagram;
		if (solutionFile != null)
			load(solutionFile);
	}

	public void load(String filename) throws FileNotFoundException {
		actions.clear();
		actors = new NamedList<PddlActor>();
		goals = null;
		solutionDiagram = null;

		Map<String, PddlActor> actor_map = new HashMap<String, PddlActor>();

		Scanner scanner = new Scanner(new File(filename));
		while (scanner.hasNextLine()) {
			String line = scanner.nextLine();
			if (!line.startsWith(";")) {
				int sp = line.indexOf('(');
				int ep = line.indexOf(')');
				if (sp >= 0 && ep > 0) {
					Action act = new Action(actions.size(), line.substring(sp + 1, ep));
					if (!act.Name().startsWith("COMBINE")) {
						PddlActor a = actor_map.get(act.Argument(0));
						if (a == null) {
							a = new PddlActor();
							a.Name = act.Argument(0);
							actor_map.put(a.Name, a);
							actors.add(a);
						}
						a.Actions.add(act);
					}
					actions.add(act);
				}
			}
		}
		scanner.close();
	}

	public String getName() {
		return name;
	}
	
	public void setName(String value) {
		name = value;
	}
	
	public int ActionCount() {
		return actions.size();
	}

	public Action getAction(int index) {
		return actions.get(index);
	}

	public Diagram SolutionDiagram() throws Exception {
		if (solutionDiagram == null)
			generateDiagram();
		return solutionDiagram;
	}

	public NamedList<PddlActor> Actors() throws Exception {
		if (actors == null)
			populateSolutionInfomation();
		return actors;
	}

	public NamedList<PddlGoal> Goals() throws Exception {
		if (goals == null)
			populateSolutionInfomation();
		return goals;
	}

	public List<Action> Actions() {
		return actions;
	}
	
	protected void generateDiagram() throws Exception {
		solutionDiagram = new Diagram();

		for (Action a : actions) {
			Method translator = actionTranslator.get(a.Name());
			if (translator != null)
				translator.invoke(null, this, solutionDiagram, a);
		}
	}

	protected void populateSolutionInfomation() throws Exception {
		actors = new NamedList<PddlActor>();
		goals = new NamedList<PddlGoal>();

		for (int i = 0; i < this.ActionCount(); i++) {
			PddlSolution.Action action = this.getAction(i);
			if ("SATISFIES".equalsIgnoreCase(action.Name())) {
				PddlActor a = actors.get(action.Argument(0));
				if (a == null) {
					a = new PddlActor();
					a.Name = action.Argument(0);
					actors.add(a);
				}
				PddlGoal g = new PddlGoal();
				g.Name = action.Argument(1);
				a.Goals.add(g);
				goals.add(g);
			}
		}
	}

	@Override
	public String toString() {
		StringBuilder res = new StringBuilder();
		res.append("Number of action: " + actions.size() + "\r\n");
		for (Action a : actions) {
			a.toString(res);
			res.append("\r\n");
		}
		return res.toString();
	}

	public List<Action> toArray() {
		return new ArrayList<Action>(actions);
	}

	
	@Override
	public Iterator<Action> iterator() {
		return actions.iterator();
	}
}
